/**    
 * @Description: 博客园地址：https://www.cnblogs.com/zumengjie/      
 * @author: 顶风少年   
 * @date: 2021年4月7日 下午4:05:11
 */

package datang.dtdb.parsing;

import java.util.Stack;

import datang.dtdb.exceptions.ParsingException;

/**
 * 解析基础类，使用不同的TokenHandler进行解析。
 */
public class GenericTokenParser {
	private final char openToken;
	private final char closeToken;
	private final char token;
	private final TokenHandler handler;

	/**
	 * 匹配openToken 匹配closeToken TokenHandler满足匹配时执行它的handlerToken(text);
	 */
	public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
		this.token = openToken.charAt(0);
		this.openToken = openToken.charAt(1);
		this.closeToken = closeToken.charAt(0);
		this.handler = handler;
	}

	/**
	 * 如果文本不包含openToken则返回源文本，否则执行TokenHandler.handlerToken(text);
	 */
	public String parse(String text) {

		// 如果文本中不包含openToken则直接返回
		if (text.indexOf(openToken) == -1) {
			return text;
		}

		// 这里使用算法保证 #{ 和 } 或者 ${ 和 } 成对出现
		// ${ #{ } } 错误
		Stack<Character> stack = new Stack<>();
		for (int i = 0; i < text.length(); i++) {
			char c = text.charAt(i);
			// TODO 此处硬编码还需有优化
			if (c == '#' || c == '$') {
				stack.push(c);
			}
			if (c == openToken) {
				Character top = stack.peek();
				if (top == null) {
					throw new ParsingException("{的左边必须是$或#");
				} else {
					stack.pop();
					stack.push(c);
				}
			}
			if (c == closeToken) {
				Character top = stack.peek();
				if (top != openToken) {
					throw new ParsingException("}的左边必须有{");
				} else {
					stack.pop();
				}
			}
		}
		if (stack.size() != 0) {
			throw new ParsingException("解析完毕队列中还有未成对的元素");
		}

		// 从头解析SQL将openToken和closeToken之间的文本交给具体的处理器
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < text.length(); i++) {
			char c = text.charAt(i);
			if (c == token) {
				i++;
				while (true) {
					char c2 = text.charAt(i);
					if (c2 == openToken) {
						i++;
						StringBuilder token = new StringBuilder();
						while (true) {
							char c1 = text.charAt(i);
							if (c1 == closeToken) {
								break;
							} else {
								token.append(c1);
								i++;
							}
						}
						sb.append(handler.handlerToken(token.toString()));
					}
					break;
				}

			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}
}
